<?php

declare(strict_types=1);

namespace Yurun\RegionData;

/**
 * 导出类接口.
 */
interface IGDExportHandler
{
    /**
     * 开始.
     *
     * @return void
     */
    public function begin();

    /**
     * 写入.
     *
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level);

    /**
     * 结束
     *
     * @return void
     */
    public function end();
}

/**
 * 导出类基类.
 */
abstract class BaseGDExportHandler implements IGDExportHandler
{
    /**
     * 应用类.
     *
     * @var App
     */
    private $app;

    /**
     * @param App $app
     */
    public function __construct($app)
    {
        $this->app = $app;
    }

    /**
     * Get 应用类.
     *
     * @return App
     */
    public function getApp()
    {
        return $this->app;
    }
}

/**
 * CSV 导出类.
 */
class GDExportCSVHandler extends BaseGDExportHandler
{
    /**
     * 保存文件名.
     *
     * @var string
     */
    private $file;

    /**
     * delimiter.
     *
     * @var string
     */
    private $delimiter;

    /**
     * enclosure.
     *
     * @var string
     */
    private $enclosure;

    /**
     * 文件句柄.
     *
     * @var resource
     */
    private $fp;

    /**
     * 开始.
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['csv'] ?? [];
        $this->file = $config['file'] ?: (__DIR__ . '/export-' . time() . '.csv');
        $this->delimiter = $config['delimiter'] ?: '"';
        $this->enclosure = $config['enclosure'] ?: ',';
        $this->fp = fopen($this->file, 'w+');
        if ($config['title'] ?? true)
        {
            fputcsv($this->fp, ['city_code', 'ad_code', 'parent_ad_code', 'name', 'longitude', 'latitude', 'level'], $this->delimiter, $this->enclosure);
        }
    }

    /**
     * 写入.
     *
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        fputcsv($this->fp, \func_get_args(), $this->delimiter, $this->enclosure);
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        fclose($this->fp);
    }
}

/**
 * MySQL 导出类.
 */
class GDExportMySQLHandler extends BaseGDExportHandler
{
    /**
     * 主机.
     *
     * @var string
     */
    private $host;

    /**
     * 端口.
     *
     * @var string
     */
    private $port;

    /**
     * 用户名.
     *
     * @var string
     */
    private $username;

    /**
     * 密码
     *
     * @var string
     */
    private $password;

    /**
     * 数据库名.
     *
     * @var string
     */
    private $dbname;

    /**
     * 数据库对象
     *
     * @var \PDO
     */
    private $db;

    /**
     * 插入的 Statement.
     *
     * @var \PDOStatement
     */
    private $insertStatement;

    /**
     * 开始.
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['mysql'] ?? [];
        $this->host = $config['host'] ?: '127.0.0.1';
        $this->port = $config['port'] ?: 3306;
        $this->username = $config['username'] ?: 'root';
        $this->password = $config['password'] ?: 'root';
        $this->dbname = $config['dbname'] ?: '';
        $this->db = new \PDO(sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8', $this->host, $this->port, $this->dbname), $this->username, $this->password);
        $this->db->exec('DROP TABLE IF EXISTS `tb_region`');
        $this->db->exec(<<<SQL
        CREATE TABLE `tb_region`  (
          `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
          `parent_ad_code` char(6) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '父级区域编码',
          `city_code` char(4) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '城市编码',
          `ad_code` char(6) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '区域编码；街道没有独有的adcode，均继承父类（区县）的adcode',
          `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '行政区名称',
          `longitude` decimal(10, 7) NOT NULL COMMENT '经度',
          `latitude` decimal(10, 7) NOT NULL COMMENT '纬度',
          `level` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '行政区划级别；country:国家；province:省份（直辖市会在province和city显示）；city:市（直辖市会在province和city显示）；district:区县；street:街道',
          PRIMARY KEY (`id`) USING BTREE,
          INDEX `city_code`(`city_code`) USING BTREE,
          INDEX `ad_code`(`ad_code`) USING BTREE,
          INDEX `level`(`level`) USING BTREE,
          INDEX `parent_ad_code`(`parent_ad_code`) USING BTREE
        ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; 
        SQL
        );
        $this->insertStatement = $this->db->prepare(<<<SQL
        insert into `tb_region`(`city_code`,`ad_code`,`parent_ad_code`,`name`,`longitude`,`latitude`,`level`) values(:cityCode,:adCode,:parentAdCode,:name,:longitude,:latitude,:level)
        SQL
        );
        $this->db->beginTransaction();
    }

    /**
     * 写入.
     *
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        $this->insertStatement->execute([
            ':cityCode'     => $cityCode,
            ':adCode'       => $adCode,
            ':parentAdCode' => $parentAdCode,
            ':name'         => $name,
            ':longitude'    => $longitude,
            ':latitude'     => $latitude,
            ':level'        => $level,
        ]);
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        $this->db->commit();
    }
}

/**
 * JSON 导出类.
 */
class GDExportJsonHandler extends BaseGDExportHandler
{
    /**
     * 列表数据的 JSON 文件名.
     *
     * @var string
     */
    private $listFileName;

    /**
     * 关联数据的 JSON 文件名.
     *
     * @var string
     */
    private $assocFileName;

    /**
     * 子地区集合的字段名.
     *
     * @var string
     */
    private $childrenField;

    /**
     * 列表数据.
     *
     * @var array
     */
    private $listData;

    /**
     * 开始.
     *
     * @return void
     */
    public function begin()
    {
        $config = $this->getApp()->getConfig()['json'] ?? [];
        $this->listFileName = $config['listFileName'] ?: '';
        $this->assocFileName = $config['assocFileName'] ?: '';
        $this->childrenField = $config['childrenField'] ?: 'children';
        $this->listData = [];
    }

    /**
     * 写入.
     *
     * @return void
     */
    public function write(string $cityCode, string $adCode, string $parentAdCode, string $name, string $longitude, string $latitude, string $level)
    {
        $this->listData[] = compact('cityCode', 'adCode', 'parentAdCode', 'name', 'longitude', 'latitude', 'level');
    }

    /**
     * 结束
     *
     * @return void
     */
    public function end()
    {
        file_put_contents($this->listFileName, json_encode($this->listData, \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR));
        file_put_contents($this->assocFileName, json_encode($this->toTreeAssoc($this->listData, 'adCode', 'parentAdCode', $this->childrenField), \JSON_UNESCAPED_UNICODE | \JSON_THROW_ON_ERROR));
    }

    /**
     * 列表转树形关联结构.
     *
     * @param string $idField
     * @param string $parentField
     * @param string $childrenField
     *
     * @return array
     */
    private function toTreeAssoc(array $list, $idField = 'id', $parentField = 'parent_id', $childrenField = 'children')
    {
        // 查出所有记录
        $result = $tmpArr = [];
        // 处理成ID为键名的数组
        foreach ($list as $item)
        {
            $item[$childrenField] = [];
            if ('street' === $item['level'])
            {
                $tmpArr[$item[$idField] . $item['name']] = $item;
            }
            else
            {
                $tmpArr[$item[$idField]] = $item;
            }
        }
        foreach ($tmpArr as $id => $item)
        {
            if (isset($tmpArr[$item[$parentField]]))
            {
                $tmpArr[$item[$parentField]][$childrenField][] = &$tmpArr[$id];
            }
            else
            {
                $result[] = &$tmpArr[$id];
            }
        }

        return $result;
    }
}

// 执行主体
class App
{
    /**
     * IGDExportHandler.
     *
     * @var IGDExportHandler
     */
    private $handler;

    /**
     * 配置.
     *
     * @var array
     */
    private $config;

    /**
     * @param string|null $configFile
     */
    public function __construct($configFile = null)
    {
        $this->config = include $configFile ?? (__DIR__ . '/gaode-config.php');
    }

    /**
     * 运行.
     *
     * @return void
     */
    public function run()
    {
        $url = sprintf('http://restapi.amap.com/v3/config/district?key=%s&keywords=中国&subdistrict=10&extensions=base', $this->config['app_key']);

        echo '正在请求高德 API 接口...', \PHP_EOL;
        $time = microtime(true);
        $content = file_get_contents($url);
        echo '接口耗时：', microtime(true) - $time, 's', \PHP_EOL;

        $data = json_decode($content, true);
        if (!$data)
        {
            echo '数据解析失败', \PHP_EOL;
            exit(1);
        }

        if (1 != ($data['status'] ?? 0))
        {
            echo '数据解析失败: ', $data['info'] ?? '', \PHP_EOL;
            exit(1);
        }

        if (!($china = ($data['districts'][0] ?? null)))
        {
            echo '未找到 中国 数据', \PHP_EOL;
            exit(1);
        }

        $handlerClass = $this->config['export_class'];
        $this->handler = new $handlerClass($this);
        $this->handler->begin();
        try
        {
            $this->writeToHandler('', $china);
        }
        finally
        {
            $this->handler->end();
        }
    }

    /**
     * 写入处理器.
     *
     * @return void
     */
    public function writeToHandler(string $parentAdcode, array $data)
    {
        [$latitude, $longitude] = explode(',', $data['center']);
        $this->handler->write($data['citycode'] ?: '', $data['adcode'], $parentAdcode, $data['name'], $longitude, $latitude, $data['level']);
        foreach ($data['districts'] as $item)
        {
            $this->writeToHandler($data['adcode'], $item);
        }
    }

    /**
     * Get 配置.
     *
     * @return array
     */
    public function getConfig()
    {
        return $this->config;
    }
}

$app = new App();
$app->run();
